醒醒吧少年,只用Cucumber不能帮助你BDD | TW洞见
今日洞见
文章作者来自ThoughtWorks:毛超。
本文所有内容,包括文字、图片和音视频资料,版权均属ThoughtWorks公司所有,任何媒体、网站或个人未经本网协议授权不得转载、链接、转贴或以其他方式复制发布/发表。已经本网协议授权的媒体、网站,在使用时必须注明"内容来源:ThoughtWorks洞见",并指定原文链接,违者本网将依法追究责任。
在Ruby社区中,测试和BDD一直是一个被热议的话题,不管是单元测试,集成测试和功能测试,你总能找到能帮助你的工具,Cucumber就是被广泛使用的工具之一。许多团队选择Cucumber的原因是“团队要BDD”,也就是行为驱动开发(Behavior Driven Development),难道用了Cucumber之后团队就真的BDD了么?
事情当然没这么简单了,BDD作为一种软件开发方法论,一定要理解其含义并且遵循特定的流程,工具只不过是起辅助作用而已。会切菜的不一定都是厨子,会写代码的不一定都是程序员。近期Cucumber的作者Aslak也在中提到
在BDD出现的9年后,依然有不少团队在使用BDD时出现问题……BDD依然经常被人误解成单纯的测试,或者是一个可以被下载的工具
同时,Aslak也吐槽了
就在最近,Cucumber已经被下载了超过500万次,我很高兴它如此受欢迎,同时也为它被广泛的误用而感到失望……Cucumber有时依然被错误的当成了自动化测试工具,而不是我当时创建的东西。
那么问题来了,怎样在日常项目中使用Cucumber呢?真的能在日常项目中进行BDD开发么?要回答这个问题,我们需要重新认识一下BDD。
2003年,开发人员Dan North偶然间发现把测试的标题经过简单的文字处理可以更好表达代码蕴含的业务逻辑,比如下面这段代码,
public class CustomerLookupTest extends TestCase {
testFindsCustomerById() {
...
}
testFailsForDuplicateCustomers() {
...
}
}
当我们把测试方法中的test去掉,给单词加上空格,然后把他们组合在一起时,就会出现:
CustomerLookup
- finds customer by id
- fails for duplicate customers
- ...
在Dan看来,这无疑是对CustomerLookup类的描述,并且是用测试内容来描述代码中类的行为。Dan发现他似乎找到了一种方式,可以在TDD的基础上,通过测试来表达代码的行为。在尝到甜头后,Dan写了JBehave,一个更关注代码行为的工具来代替JUnit进行软件开发。经过一番折腾后,Dan觉得只描述类行为不过瘾,便开始把关注点从类扩展到整个软件,他和当时项目组的业务人员一起把需求转化成Given/When/Then的三段式,然后用JBehave写成测试来描述软件的某种行为。当测试完成后,开发人员才开始编码,一旦测试通过,那软件就完成了测试中描述的某种行为。在他看来,他把TDD升级了,因为他不再只关注于局部类的方法,而开始关注整个软件的行为。
通过这种方式,Dan成功的把需求转换成了软件的功能测试,先写功能测试再驱动出产品代码,保证软件行为正确性。其次,Dan强调在测试中要尽可能的使用业务词汇,保证团队成员对业务理解一致。于是,BDD就此诞生。
在上面的故事中,“测试”这个词出现了很多次,你是不是已经认为BDD就是用功能测试驱动产品代码的开发流程呢?其实不然,功能测试只是一个结果而已,更重要的是和业务人员一起分析需求,沟通交流来产生测试的过程。用测试驱动出来的代码可以保证是正确的,但如何保证测试是正确的呢?答案就是人,通过业务,开发和测试一起参与生成的测试文档,不仅能保证软件功能上是正确的,还能保证团队成员对业务理解是一致的。在测试文档中,也应该尽量保证使用自然语言和业务词汇,减少非技术人员的学习成本。
在多年之后,Dan也终于给出了他对
BDD是第二代的、由外及内的、基于拉(Pull)的、多方利益相关者的(Stakeholder)、多种可扩展的、高自动化的敏捷方法。它描述了一个交互循环,可以具有带有良好定义的输出(即工作中交付的结果):已测试过的软件。
Cucumber的另一位作者Matt Wynne也给出了
BDD的实践者们通过沟通交流,具体的示例和自动化测试帮助他们更好地探索,发现,定义并驱动出人们真正想用的软件
从上述定义我们可以看出,BDD更强调流程和一系列实践,自动化测试只是其中一部分而已。
4Cucumber到底怎么用理解了BDD的精髓后,我们就不难找出正确的使用Cucumber的方式了。根据Cucumber的定义,它的核心就是Specification,其实就是文档化的需求。Specification是通过Requrement Workshop生成的,在Workshop中业务,开发和测试一起分析需求,把需求用自然语言写成文档,然后再转换成Given/When/Then的Specification文件,这样便完成了BDD中最重要的一步,定义软件正确的行为。接着开发人员开始编码,完成相应需求,保证Specification文件运行通过,整个流程结束。
简单来说,Cucumber其实不是一个自动化测试工具,而是一个促进团队沟通合作的工具。但由于Cucumber无法确保上述流程真正的发生,有很多团队简化或者跳过了Workshop,直接开始写Specification文件,没有沟通就很难保证理解一致,Bug也许就在那时潜伏了下来。这样大家也就不难理解作者吐槽的“Cucumber被广泛的误用”,其实Cucumber只是一个沟通工具,它只是刚巧可以运行测试而已。
任何工具和实践都有优缺点,Cucumber也不例外。团队在开始尝试新的实践或者工具时,多多少少都会碰到一些问题,下面我们就来看看一些使用Cucumber的问题。
没有业务人员参与的Specification
要么业务人员没时间写Specification,交给其他人写,写完之后业务人员也没时间去审核。在这种情况下,很难保证Specification的业务正确性,一旦Specification出现问题,团队可能发生理解不一致,甚至做错需求的现象。反过来看,Specification文件由自然语言而不是代码组成,也能反映出对非技术人员参与的重视程度。然而现实情况很难保证业务、测试、开发有充足的时间进行Specification的讨论和编写,这也是导致业务人员逐渐脱离Specification的主要原因。
Specification关注实现细节而不是业务逻辑
Cucumber使用自然语言描述业务需求,然而不少团队都陷入到了实现细节中。比如
Scenario: Detect agent type based on contract number
Given I am on the 'Find me' page
And I have entered a contract number
When I click 'Continue' button
And a contact number match is found
Then the "Back" button will be displayed
上面的描述满篇是点击了那个按钮,输入了什么内容,看完之后反而让人有点困惑,用户到底为什么要做这些,做了之后有什么价值。这样的Specification既不能满足团队成员对业务需求的了解,也会由于界面的细微改动运行失败。
Step的嵌套调用
Specification文件由Step组成,在Step中我们可以通过Ruby进行自动化的页面操作。有时我们会发现某些Specification会重复进行一系列的操作,这时我们就可以把重复的Step进行组合,创建出新的Step。比如这样
Given there is student Harry
And there is professor Snape
And student Harry joins class of professor Snape
# use 1 new step instead of 3
Given student Harry in class of professor Snape
那么这个新的Step该怎么实现呢?Cucumber支持在Step中调用Step,比如这样
Given /^student (.*) in class of professor (.*)/ do |student, professor|
step "there is student #{student}"
step "there is professor #{professor}"
step "student #{student} joins class of professor #{professor}"
end
乍一看好像没什么问题,其实不然。Step使用正则表达式进行匹配,问题恰恰出在正则上。
首先,正则灵活性很大,你确定上面例子中step “there is student #{student}”一定会调用到你想要调用的Step么?你无法确定在运行时,是否会出现另一个Step “there is student come from China”来截胡。
其次,正则逆推难度很大,也就是说当你看到“^(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]).(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0).(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0).(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])$”时,你很难看出这是在匹配IP地址。所以当我们需要修改step时,很难确定有多少个step在依赖它,这也加大了维护成本。
最后,嵌套次数过多的Step也会导致代码复杂,难以理解。
Specification Report可读性不高
Specification除了是自动化测试的描述文件之外,更重要的是软件的“活文档”。有时我们需要通过“活文档”进行知识传递。Cucumber虽然提供生成Report功能,但效果未免有些差强人意。比如下面
满篇绿色的Step,再加上Given/When/Then来捣乱,这样的Report只是运行结果而已,可读性很差,很难当成软件需求文档。究其原因,主要因为Cucumber Report的表现力差。
首先,它只支持纯文本,在这个“一图胜千言,无图无真相”的时代很难只通过文字来描述复杂业务,如果能在文档中加上图片,甚至一段视频,都会帮助我们更容易的理解复杂业务。
各类干货洞见精选职位招聘
活动预告总结长按二维码可直接关注我们~